#include "defaults.h"
#include "inplace.h"
#include "config.h"

// Modified scratch program from
// http://blogs.msdn.com/oldnewthing/archive/2003/07/23/54576.aspx

// Application instance
HINSTANCE g_hinst;
// Main dialog handle
HWND g_hwnd_dlg = NULL;
// Window for resizing
HWND g_hwnd_target = NULL;
BOOL g_searching = FALSE;
// Finder icons
HICON g_icon_finder_normal = NULL;
HICON g_icon_finder_empty = NULL;
// Finder cursor
HCURSOR g_cursor_finder = NULL;
HCURSOR g_cursor_finder_previous = NULL;
// Finder highlighter pen
HPEN g_pen_finder = NULL;

// SetDlgItem* methods are sending EN_CHANGED, so we need to prevent it somehow
bool g_prevent_pos_calc = false;
bool g_prevent_size_calc = false;
// Special masking values for setPosition and setSize
#define INVALID_POSITION XINT_MIN
#define INVALID_SIZE XINT_MIN

// Hotspot position of a finder cursor on the finder dock
#define FINDER_CENTER_X 15
#define FINDER_CENTER_Y 15

// Main configuration
Config g_cfg_main;

#define CFG_KEEP_ON_TOP _T("keep_on_top")
#define CFG_HIDE_AUTOMATICALLY _T("hide_automatically")

#define CFG_LEFT _T("left")
#define CFG_TOP _T("top")
#define CFG_WIDTH _T("width")
#define CFG_HEIGHT _T("height")

#define CFG_MAIN_LEFT _T("main_left")
#define CFG_MAIN_TOP _T("main_top")

#define CFG_VCENTER _T("center_vertically")
#define CFG_HCENTER _T("center_horizontally")
#define CFG_RATIO _T("keep_ratio")

//------------------------------------------------------------------------
void getMonitorRect( RECT & o_rc_monitor )
//------------------------------------------------------------------------
{
	ZeroMemory( &o_rc_monitor, sizeof(RECT) );

	HMONITOR hmonitor = MonitorFromWindow( g_hwnd_target, MONITOR_DEFAULTTONEAREST );
	if( hmonitor )
	{
		MONITORINFO mi;
		ZeroMemory( &mi, sizeof( MONITORINFO ) );
		mi.cbSize = sizeof( MONITORINFO );
		::GetMonitorInfo( hmonitor, &mi );
		o_rc_monitor = mi.rcMonitor;
	}
	else
	{
		WINDOWPLACEMENT wp;
		wp.length = sizeof( WINDOWPLACEMENT );
		ZeroMemory( &wp, sizeof( WINDOWPLACEMENT ) );
		::GetWindowPlacement( GetDesktopWindow(), &wp );
		o_rc_monitor = wp.rcNormalPosition;
	}
}

//------------------------------------------------------------------------
// Helper for calculating and storing ratio
double getRatio( bool force_update )
//------------------------------------------------------------------------
{
	static double ratio = 0.0f;

	if( force_update || ratio == 0.0f )
	{
		BOOL translated = FALSE;

		int w = GetDlgItemInt( g_hwnd_dlg, IDC_E_WIDTH, &translated, FALSE );
		if( !translated )
		{
			return 0.0f;
		}

		int h = GetDlgItemInt( g_hwnd_dlg, IDC_E_HEIGHT, &translated, FALSE );
		if( !translated )
		{
			return 0.0f;
		}

		ratio = (double)(w) / (double)(h);
	}

	return ratio;
}

//------------------------------------------------------------------------
void setPositionValues( int left, int top )
//------------------------------------------------------------------------
{
	g_prevent_pos_calc = true;
	if( left != INVALID_POSITION ) ::SetDlgItemInt( g_hwnd_dlg, IDC_E_LEFT, left, TRUE );
	if( top != INVALID_POSITION ) ::SetDlgItemInt( g_hwnd_dlg, IDC_E_TOP, top, TRUE );
	g_prevent_pos_calc = false;
}

//------------------------------------------------------------------------
void setSizeValues( int width, int height, bool update_ratio )
//------------------------------------------------------------------------
{
	g_prevent_size_calc = true;
	if( width != INVALID_SIZE ) ::SetDlgItemInt( g_hwnd_dlg, IDC_E_WIDTH, width, FALSE );
	if( height != INVALID_SIZE ) ::SetDlgItemInt( g_hwnd_dlg, IDC_E_HEIGHT, height, FALSE );
	if( update_ratio )
	{
		getRatio( true );
	}
	g_prevent_size_calc = false;
}

//------------------------------------------------------------------------
void calculateRatioFor( WORD ctl_id )
//------------------------------------------------------------------------
{
	if( g_prevent_size_calc == true )
		return;

	if( ::IsDlgButtonChecked( g_hwnd_dlg, IDC_CH_RATIO ) )
	{
		// Get width
		BOOL translated = FALSE;
		double ratio = getRatio( false );

		if( ratio != 0.0f )
		{
			if( ctl_id == IDC_E_WIDTH )
			{
				UINT w = ::GetDlgItemInt( g_hwnd_dlg, IDC_E_WIDTH, &translated, FALSE );
				if( translated )
				{
					// Calculate height
					setSizeValues( INVALID_SIZE, (UINT)( ((double)w) / ratio ), false );
				}
				else
				{
					 // If value is invalid, set 0
					setSizeValues( INVALID_SIZE, 0, false );
				}
			}
			else if( ctl_id == IDC_E_HEIGHT )
			{
				int h = GetDlgItemInt( g_hwnd_dlg, IDC_E_WIDTH, &translated, FALSE );
				if( translated )
				{
					setSizeValues( (UINT)( ((double)h) * ratio ), INVALID_SIZE, false );
				}
				else
				{
					// If value is invalid set 0
					setSizeValues( 0, INVALID_SIZE, false );
				}
			}
		}
	}
}

//------------------------------------------------------------------------
void calculateCenterFor( WORD ctl_id )
//------------------------------------------------------------------------
{
	if( g_prevent_pos_calc == true )
		return;

	RECT rc_monitor;
	if( ctl_id == IDC_E_LEFT && IsDlgButtonChecked( g_hwnd_dlg, IDC_CH_HCENTER ) )
	{
		getMonitorRect( rc_monitor );

		BOOL translated = FALSE;
		int w = ::GetDlgItemInt( g_hwnd_dlg, IDC_E_WIDTH, &translated, FALSE );
		if( translated )
		{
			int c = ( rc_monitor.right - rc_monitor.left - w ) >> 1;
			setPositionValues( rc_monitor.left + c, INVALID_POSITION );
		}
	}
	else if( ctl_id == IDC_E_TOP && IsDlgButtonChecked( g_hwnd_dlg, IDC_CH_VCENTER ) )
	{
		getMonitorRect( rc_monitor );

		BOOL translated = FALSE;
		int h = ::GetDlgItemInt( g_hwnd_dlg, IDC_E_HEIGHT, &translated, FALSE );
		if( translated )
		{
			int c = ( rc_monitor.bottom - rc_monitor.top - h ) >> 1;
			setPositionValues( INVALID_POSITION, rc_monitor.top + c );
		}
	}
}

//------------------------------------------------------------------------
void onApply()
//------------------------------------------------------------------------
{
	if( g_hwnd_target )
	{
		WINDOWPLACEMENT wp;
		ZeroMemory( &wp, sizeof(WINDOWPLACEMENT) );

		::GetWindowPlacement( g_hwnd_target, &wp );

		// #5: Work with maximixed issues
		wp.showCmd = SW_SHOWNORMAL;

		wp.rcNormalPosition.left = ::GetDlgItemInt( g_hwnd_dlg, IDC_E_LEFT, NULL, TRUE );
		wp.rcNormalPosition.top = ::GetDlgItemInt( g_hwnd_dlg, IDC_E_TOP, NULL, TRUE );
		wp.rcNormalPosition.right = ::GetDlgItemInt( g_hwnd_dlg, IDC_E_WIDTH, NULL, FALSE ) + wp.rcNormalPosition.left;
		wp.rcNormalPosition.bottom = ::GetDlgItemInt( g_hwnd_dlg, IDC_E_HEIGHT, NULL, FALSE ) + wp.rcNormalPosition.top;

		::SetWindowPlacement( g_hwnd_target, &wp );
	}
}

//------------------------------------------------------------------------
void onOpen()
//------------------------------------------------------------------------
{
	// Not supported
	// ToDo: onOpen
}

//------------------------------------------------------------------------
void onSave()
//------------------------------------------------------------------------
{
	// Not supported
	// ToDo: onOpen
}

//------------------------------------------------------------------------
void setFinderImage( BOOL normal )
//------------------------------------------------------------------------
{
	SendDlgItemMessage
	(
		g_hwnd_dlg,
		(int)IDC_S_FINDER,
		(UINT)STM_SETIMAGE,
		(WPARAM)IMAGE_ICON,
		(LPARAM)( normal ? g_icon_finder_normal : g_icon_finder_empty )
	);
}

//------------------------------------------------------------------------
void onFind()
//------------------------------------------------------------------------
{
	g_searching = TRUE;
	setFinderImage( FALSE );

	// Put the mouse to the center of the finder
	HWND finder = ::GetDlgItem( g_hwnd_dlg, IDC_S_FINDER );
	RECT rect;
	::GetWindowRect( finder , &rect );
	::SetCursorPos( rect.left + FINDER_CENTER_X, rect.top + FINDER_CENTER_Y );

	// Set the finder cursor
	g_cursor_finder_previous = g_cursor_finder ? ::SetCursor( g_cursor_finder ) : NULL;

	// Capture mouse
	::SetCapture( g_hwnd_dlg );
}

//------------------------------------------------------------------------
void cleanHighlight( HWND hwnd )
//------------------------------------------------------------------------
{
	// Use parent if available
	HWND hwnd_parent = ::GetParent( hwnd );
	if( hwnd_parent )
	{
		hwnd = hwnd_parent;
	}
	::InvalidateRect( hwnd, NULL, TRUE );
	::UpdateWindow( hwnd );
	::RedrawWindow( hwnd, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASENOW | RDW_ALLCHILDREN );
}

//------------------------------------------------------------------------
void setHighlight( HWND hwnd )
//------------------------------------------------------------------------
{
	RECT rect;
	::GetWindowRect( hwnd, &rect );

	// Get the window DC of the found window.
	HDC hdc = GetWindowDC( hwnd );

	if( hdc )
	{
		// Select our created pen into the DC and backup the previous pen.
		HPEN pen_prev = SelectPen( hdc, g_pen_finder );
		// Select a transparent brush into the DC and backup the previous brush.
		HBRUSH brush_prev = SelectBrush( hdc, GetStockObject( HOLLOW_BRUSH ) );

		// Draw a rectangle in the DC covering the entire window area of the found window.
		::Rectangle( hdc, 0, 0, rect.right - rect.left, rect.bottom - rect.top );

		// Cleanup
		SelectPen( hdc, pen_prev );
		SelectBrush( hdc, brush_prev );
		::ReleaseDC( hwnd, hdc );
	}
}

//------------------------------------------------------------------------
void onMouseUp()
//------------------------------------------------------------------------
{
	if( g_searching )
	{
		setFinderImage( TRUE );
		::ReleaseCapture();
		g_searching = FALSE;

		if( g_cursor_finder_previous )
		{
			::SetCursor( g_cursor_finder_previous );
		}

		if( g_hwnd_target )
		{
			cleanHighlight( g_hwnd_target );
		}

		// Set the coordinates
		RECT rect;
		::GetWindowRect( g_hwnd_target, &rect );
		setPositionValues( rect.left, rect.top );
		setSizeValues( rect.right - rect.left, rect.bottom - rect.top, true );
	}
}

//------------------------------------------------------------------------
bool isValidTarget( HWND hwnd )
//------------------------------------------------------------------------
{
	if( hwnd == NULL || ::IsWindow(hwnd) == FALSE )
	{
		return false;
	}

	// Ensure that the window is not the current one which has already been found.
	if( hwnd == g_hwnd_target )
	{
		return false;
	}

	// Not a dialog nor it's children
	if( hwnd == g_hwnd_dlg || ::IsChild( g_hwnd_dlg, hwnd ) == TRUE )
	{
		return false;
	}

	return true;
}

//------------------------------------------------------------------------
void onMouseMove()
//------------------------------------------------------------------------
{
	if( g_searching )
	{
		POINT pt_mouse;
		::GetCursorPos( &pt_mouse );

		// Set the current mouse position
		// ToDo: This should maybe be displayed elsewhere and not in the edits
		setPositionValues( pt_mouse.x, pt_mouse.y );

		// Determine the window that lies underneath the mouse cursor.
		HWND hwnd_target = ::WindowFromPoint( pt_mouse );

		// Check first for validity.
		if( isValidTarget( hwnd_target ) )
		{
			// Clean highlight from previous target
			if( g_hwnd_target )
			{
				cleanHighlight( g_hwnd_target );
			}
			g_hwnd_target = hwnd_target;
			setHighlight( g_hwnd_target );
		}
	}
}

//------------------------------------------------------------------------
void onPositionEditChanged( WORD id )
//------------------------------------------------------------------------
{
	// NOP
}

//------------------------------------------------------------------------
void onSizeEditChanged( WORD id )
//------------------------------------------------------------------------
{
	switch( id )
	{

	case IDC_E_WIDTH:
		calculateCenterFor( IDC_E_LEFT );
		calculateRatioFor( IDC_E_WIDTH );
		break;

	case IDC_E_HEIGHT:
		calculateCenterFor( IDC_E_TOP );
		calculateRatioFor( IDC_E_HEIGHT );
		break;

	}
}

//------------------------------------------------------------------------
void onHorizontalCenter( BOOL checked )
//------------------------------------------------------------------------
{
	::EnableWindow( ::GetDlgItem( g_hwnd_dlg, IDC_E_LEFT ), !checked );
	calculateCenterFor( IDC_E_LEFT );
}

//------------------------------------------------------------------------
void onVerticalCenter( BOOL checked )
//------------------------------------------------------------------------
{
	::EnableWindow( ::GetDlgItem( g_hwnd_dlg, IDC_E_TOP ), !checked );
	calculateCenterFor( IDC_E_TOP );
}

//------------------------------------------------------------------------
void onRatio( BOOL checked )
//------------------------------------------------------------------------
{
	if( checked )
	{
		// Update ratio
		getRatio( true );
	}
}

//------------------------------------------------------------------------
void onHideAutomatically( BOOL checked )
//------------------------------------------------------------------------
{
	// Not supported yet
}

//------------------------------------------------------------------------
void onKeepOnTop( BOOL checked )
//------------------------------------------------------------------------
{
	if( checked )
	{
		::SetWindowPos( g_hwnd_dlg, checked ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW );
	}
}

//------------------------------------------------------------------------
void loadPreset( Config & cfg )
//------------------------------------------------------------------------
{
	setPositionValues( cfg.readInt( CFG_LEFT, 0 ), cfg.readInt( CFG_TOP, 0 ) );
	setSizeValues( cfg.readInt( CFG_WIDTH, 0 ), cfg.readInt( CFG_HEIGHT, 0 ), true );

	if( cfg.readInt( CFG_VCENTER, true ) )
	{
		::CheckDlgButton( g_hwnd_dlg, IDC_CH_VCENTER, TRUE );
		onVerticalCenter( TRUE );
	}

	if( cfg.readInt( CFG_HCENTER, true ) )
	{
		::CheckDlgButton( g_hwnd_dlg, IDC_CH_HCENTER, TRUE );
		onHorizontalCenter( TRUE );
	}

	if( cfg.readInt( CFG_RATIO, true ) )
	{
		::CheckDlgButton( g_hwnd_dlg, IDC_CH_RATIO, TRUE );
		onRatio( TRUE );
	}
}

//------------------------------------------------------------------------
void savePreset( Config & cfg )
//------------------------------------------------------------------------
{
	// ToDo
}

//------------------------------------------------------------------------
HWND onCreate( HWND hwnd_focus )
//------------------------------------------------------------------------
{
	if( g_cfg_main.readBool( CFG_KEEP_ON_TOP, true ) )
	{
		::CheckDlgButton( g_hwnd_dlg, IDC_CH_ONTOP, TRUE );
		onKeepOnTop( true );
	}

	if( g_cfg_main.readBool( CFG_HIDE_AUTOMATICALLY, true ) )
	{
		::CheckDlgButton( g_hwnd_dlg, IDC_CH_HIDE, TRUE );
		onHideAutomatically( true );
	}

	int left = g_cfg_main.readInt( CFG_MAIN_LEFT, 20 );
	int top = g_cfg_main.readInt( CFG_MAIN_TOP, 20 );
	::SetWindowPos( g_hwnd_dlg, NULL, left, top, 0, 0, SWP_NOSIZE | SWP_NOZORDER );

	// Load most recent values
	loadPreset( g_cfg_main );

	::SendMessage( g_hwnd_dlg, WM_SETTEXT, 0, (LPARAM)_T("Inplace ")_T(INPLACE_VERSION_STRING) );

	return NULL;
}

//------------------------------------------------------------------------
void onDestroy()
//------------------------------------------------------------------------
{
	RECT rect;
	::GetWindowRect( g_hwnd_dlg, &rect );
	g_cfg_main.writeInt( CFG_MAIN_LEFT, rect.left );
	g_cfg_main.writeInt( CFG_MAIN_TOP, rect.top );

	g_cfg_main.writeBool( CFG_KEEP_ON_TOP, ::IsDlgButtonChecked( g_hwnd_dlg, IDC_CH_ONTOP ) == TRUE );
	g_cfg_main.writeBool( CFG_HIDE_AUTOMATICALLY, ::IsDlgButtonChecked( g_hwnd_dlg, IDC_CH_HIDE ) == TRUE );
	savePreset( g_cfg_main );
}

//------------------------------------------------------------------------
// Dialog procedure
BOOL CALLBACK dialogProcedure( HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam )
//------------------------------------------------------------------------
{
	BOOL ret = FALSE;

	switch( uiMsg )
	{
	//--------------------------------------------------------------------
	case WM_INITDIALOG:
	//--------------------------------------------------------------------
		{
			// Remember the dlg
			g_hwnd_dlg = hwnd;

			HWND hwnd_focus = onCreate( (HWND)wParam );
			if( hwnd_focus != NULL )
			{
				::SetWindowLong( hwnd, DWL_MSGRESULT, (LONG)hwnd_focus );
				ret = FALSE;
			}
			else
			{
				ret = TRUE;
			}
		}
		break;

	//--------------------------------------------------------------------
	case WM_DESTROY:
	//--------------------------------------------------------------------
		onDestroy();
		break;

	//--------------------------------------------------------------------
	case WM_COMMAND:
	//--------------------------------------------------------------------
		{
			WORD code = HIWORD(wParam);
			WORD ctl_id = LOWORD(wParam);
			HWND ctl_hwnd = (HWND)lParam;

			if( code == EN_CHANGE )
			{
				switch( ctl_id )
				{
				case IDC_E_TOP:
				case IDC_E_LEFT:
					onPositionEditChanged( ctl_id );
					ret = TRUE;
					break;

				case IDC_E_WIDTH:
				case IDC_E_HEIGHT:
					onSizeEditChanged( ctl_id );
					ret = TRUE;
					break;
				}
			}
			else
			{
				ret = TRUE;

				switch( ctl_id )
				{
				case IDCANCEL: case IDOK:
					::EndDialog( g_hwnd_dlg, ctl_id );
					break;

				case IDC_S_FINDER:
					onFind();
					break;

				case IDC_B_OPEN:
					onOpen();
					break;

				case IDC_B_SAVE:
					onSave();
					break;

				case IDC_B_APPLY:
					onApply();
					break;

				case IDC_CH_HIDE:
					onHideAutomatically( IsDlgButtonChecked( g_hwnd_dlg, IDC_CH_HIDE ) );
					break;

				case IDC_CH_ONTOP:
					onKeepOnTop( IsDlgButtonChecked( g_hwnd_dlg, IDC_CH_ONTOP ) );
					break;

				case IDC_CH_HCENTER:
					onHorizontalCenter( IsDlgButtonChecked( g_hwnd_dlg, IDC_CH_HCENTER ) );
					break;

				case IDC_CH_VCENTER:
					onVerticalCenter( IsDlgButtonChecked( g_hwnd_dlg, IDC_CH_VCENTER ) );
					break;

				case IDC_CH_RATIO:
					onRatio( IsDlgButtonChecked( g_hwnd_dlg, IDC_CH_RATIO ) );
					break;

				default:
					ret = FALSE;
					break;
				}
			}
		}
		break;

	//------------------------------------------------------------------------
	case WM_LBUTTONUP:
	//------------------------------------------------------------------------
		onMouseUp();
		break;

	//------------------------------------------------------------------------
	case WM_MOUSEMOVE:
		onMouseMove();
		break;
	}

	return ret;
}

//------------------------------------------------------------------------
bool initConfiguration()
//------------------------------------------------------------------------
{
	const static int MAX_SIZE = 1024;
	TCHAR * dir = new TCHAR[ MAX_SIZE ];
	dir[ MAX_SIZE - 1 ] = 0;
	dir[ 0 ] = 0;
	_tcscat( dir, _wgetenv( _T( "AppData" ) ) );
	_tcscat( dir, _T( "\\inplace.ini" ) );
	return g_cfg_main.open( dir );
}

//------------------------------------------------------------------------
bool initApplication()
//------------------------------------------------------------------------
{
	InitCommonControls();
	
	if( initConfiguration() == FALSE)
	{
		return FALSE;
	}

	g_cursor_finder = ::LoadCursor( g_hinst, MAKEINTRESOURCE( IDC_FINDER ) );
	if( g_cursor_finder == NULL )
	{
		return FALSE;
	}

	g_pen_finder = ::CreatePen( PS_SOLID, 5, RGB(255, 0, 0) );
	if( g_pen_finder == NULL )
	{
		return FALSE;
	}

	g_icon_finder_normal = ::LoadIcon( g_hinst, MAKEINTRESOURCE(IDI_FINDER_NORMAL) );
	if( g_icon_finder_normal == NULL )
	{
		return FALSE;
	}

	g_icon_finder_empty = ::LoadIcon( g_hinst, MAKEINTRESOURCE(IDI_FINDER_EMPTY) );
	if( g_icon_finder_empty == NULL )
	{
		return FALSE;
	}

	return TRUE;
}

//------------------------------------------------------------------------
void releaseApplication()
//------------------------------------------------------------------------
{
	// Cleanup resources
	::DestroyCursor( g_cursor_finder );
	DeletePen( g_pen_finder );
	::DestroyIcon( g_icon_finder_normal );
	::DestroyIcon( g_icon_finder_empty );
}

//------------------------------------------------------------------------
int WINAPI WinMain( HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nShowCmd )
//------------------------------------------------------------------------
{
	// Remember the application instance
	g_hinst = hinst;

	if( initApplication() )
	{
		// In case we use COM
		if( SUCCEEDED( CoInitialize(NULL) ) )
		{
			g_hinst = hinst;

			(HWND)DialogBox
				(
				g_hinst,
				(LPCTSTR)MAKEINTRESOURCE(IDD_MAIN),
				(HWND)NULL,
				(DLGPROC)dialogProcedure
				);

			CoUninitialize();
		}
	}

	releaseApplication();


	return 0;
}